import logging
from google.appengine.api import urlfetch
from google.appengine.ext import db, deferred
from google.appengine.runtime import DeadlineExceededError
from application.models import Alliance, Map, Tower
from flask import json


class ListMapper(object):
	"""Mapper object to work through a list of objects doing something with each one."""
	
	queue = "default"
	
	def __init__(self, queue='default'):
		self.queue = queue
		self.to_put = []
		self.to_delete = []
		
	def map(self, entity):
		"""Processes a single entry from the API response
		
		Implementors should return a tuple containing two iterables (to_update, to_delete
		"""
		return ([], [])
	
	def finish(self):
		"""Called when the mapper has finished, to allow for any final work to be done."""
		logging.debug('Mapper.finish()')
	
	def run(self, records, batch_size=50):
		"""Starts the mapper running."""
		logging.debug('Mapper.run()')
		self._continue(records, batch_size)
		
	def _batchWrite(self):
		logging.debug('Mapper._batchWrite()')
		if self.to_put:
			logging.debug('Pushing %d objects to datastore' % len(self.to_put))
			db.put(self.to_put)
			self.to_put = []
		if self.to_delete:
			logging.debug('Deleting %d objects from datastore' % len(self.to_delete))
			db.delete(self.to_delete)
			self.to_delete = []
			
	def _continue(self, records, batch_size):
		logging.debug('Mapper._continue()')
		try:
			i = 0;
			for entity in records:
				map_updates, map_deletes = self.map(entity)
				self.to_put.extend(map_updates)
				self.to_delete.extend(map_deletes)
				# Do updates and deletes in batches
				if (i + 1) % batch_size == 0:
					self._batchWrite()
				i = i + 1
			self._batchWrite()
		except DeadlineExceededError:
			# Write any unfinished updates to the datastore
			self._batchWrite()
			logging.debug('Max time reached, defering')
			deferred.defer(self._continue, records[i:], batch_size, _queue=self.queue, _countdown=2)
			return
		self.finish()

class AllianceMapper(ListMapper):
	def map(self, entity):
		alliance, updated = Alliance.updateOrAddFromApi(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(alliance)
		return (mapper_update, mapper_delete)

class SolarSystemSovMapper(ListMapper):
	def map(self, entity):
		solarSystem, updated = Map.SolarSystem.updateOrAddFromSovApi(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(solarSystem)
		return (mapper_update, mapper_delete)

class ImportMapper(ListMapper):
	dataSource = None
	interface = None

	def __init__(self, dataSource, **kwargs):
		super(ImportMapper, self).__init__(**kwargs)
		self.dataSource = dataSource
		
	def runMapper(self):
		if self.dataSource is None or self.interface is None:
			return
		url = self.dataSource + self.interface
		result = urlfetch.fetch(url)
		if result.status_code == 200:
			logging.debug('Got JSON deferring processing')
			#logging.debug(result.content)
			records = json.loads(result.content)
			deferred.defer(self.run, records, _queue="importer", _countdown=2)
			
class ImportRegionsMapper(ImportMapper):
	interface = 'locations.php?mode=regions'
	
	def map(self, entity):
		region, updated = Map.Region.updateOrAddFromJson(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(region)
		return (mapper_update, mapper_delete)
	
	def finish(self):
		logging.info(u'Importer: Region Import Complete')
		mapper = ImportConstellationsMapper(self.dataSource)
		deferred.defer(mapper.runMapper, _queue="importer", _countdown=2)
		
class ImportConstellationsMapper(ImportMapper):
	interface = 'locations.php?mode=constellations'
	
	def map(self, entity):
		constellation, updated = Map.Constellation.updateOrAddFromJson(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(constellation)
		return (mapper_update, mapper_delete)
	
	def finish(self):
		logging.info(u'Importer: Constellation Import Complete')
		ImportSolarSystemsMapper.runAllMappers(self.dataSource)
		
class ImportSolarSystemsMapper(ImportMapper):
	dataSource = None
	interface = 'locations.php?mode=systems'
	prefix = None

	def __init__(self, dataSource, prefix=None, **kwargs):
		super(ImportSolarSystemsMapper, self).__init__(dataSource, **kwargs)
		if prefix is not None:
			self.prefix = prefix
			self.interface = self.interface + '&prefix=' + prefix

	def map(self, entity):
		solarSystem, updated = Map.SolarSystem.updateOrAddFromJson(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(solarSystem)
		return (mapper_update, mapper_delete)
	
	def finish(self):
		logging.info('Importer: Solar System "%s" Import Complete' % self.prefix)
		#ImportMoonsMapper.runAllMappers(self.dataSource, self.prefix)
	
	@classmethod
	def runAllMappers(cls, dataSource):
		for prefix in map(chr, range(97, 123)) + range(0, 10):
			mapper = ImportSolarSystemsMapper(dataSource, str(prefix))
			deferred.defer(mapper.runMapper, _queue="importer", _countdown=2) 

class ImportMoonsMapper(ImportMapper):
	dataSource = None
	interface = 'locations.php?mode=moons'
	prefix = None
	
	def __init__(self, dataSource, region=None, prefix=None, **kwargs):
		super(ImportMoonsMapper, self).__init__(dataSource, **kwargs)
		if prefix is not None:
			self.prefix = prefix
			self.interface = self.interface + '&prefix=' + prefix
		if region is not None:
			self.region = region
			self.interface = self.interface + '&region=' + str(region.key().id_or_name())

	def map(self, entity):
		moon, updated = Map.Moon.updateOrAddFromJson(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(moon)
		return (mapper_update, mapper_delete)
	
	def finish(self):
		logging.info('Importer: Moon "%s" Import Complete' % self.prefix)

	@classmethod
	def runAllMappers(cls, dataSource, region, prefix=''):
		for postfix in map(chr, range(97, 123)) + range(0, 10):
			mapper = ImportMoonsMapper(dataSource, region, str(prefix) + str(postfix))
			deferred.defer(mapper.runMapper, _queue="importer", _countdown=2) 

class ImportTowerFuelTypeMapper(ImportMapper):
	interface = 'towers.php?mode=fuel-types'
	
	def map(self, entity):
		fuel, updated = Tower.FuelType.updateOrAddFromJson(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(fuel)
		return (mapper_update, mapper_delete)
	
	def finish(self):
		logging.info(u'Importer: Tower FuelType Import Complete')
		mapper = ImportTowerTypeMapper(self.dataSource)
		deferred.defer(mapper.runMapper, _queue="importer", _countdown=2)

class ImportTowerTypeMapper(ImportMapper):
	interface = 'towers.php?mode=towers'
	attributeInterface = 'towers.php?mode=type-attributes'
	
	def map(self, entity):
		type, updated = Tower.Type.updateOrAddFromJson(entity)
		url = self.dataSource + self.attributeInterface + '&typeId=' + type.key().id_or_name()
		result = urlfetch.fetch(url)
		attributesUpdated = False
		if result.status_code == 200:
			logging.debug('Got Attribute JSON Processing')
			records = json.loads(result.content)
			attributesUpdated = type.updateTypeAttributesFromJson(records)
		mapper_update = []
		mapper_delete = []
		if updated or attributesUpdated:
			mapper_update.append(type)
		return (mapper_update, mapper_delete)
	
	def finish(self):
		logging.info(u'Importer: Tower Type Import Complete')
		mapper = ImportTowerFuelMapper(self.dataSource)
		deferred.defer(mapper.runMapper, _queue="importer", _countdown=2)

class ImportTowerFuelMapper(ImportMapper):
	interface = 'towers.php?mode=tower-fuel'
	
	def map(self, entity):
		fuel, updated = Tower.Fuel.updateOrAddFromJson(entity)
		mapper_update = []
		mapper_delete = []
		if updated:
			mapper_update.append(fuel)
		return (mapper_update, mapper_delete)
	
	def finish(self):
		logging.info(u'Importer: Tower Fuel Import Complete')
		#mapper = ImportTowerTypeMapper(self.dataSource)
		#deferred.defer(mapper.runMapper, _queue="importer", _countdown=2)
	